﻿using System.Runtime.InteropServices;
using Tnelab.Common;

namespace Tnelab.Miniblink
{
    class VipWebView : IWebView
    {
        readonly object js_call_native_event_arg_dic_lock = new object();
        readonly Dictionary<Int64, JsCallNativeEventArgs> js_call_native_event_arg_dic = new Dictionary<long, JsCallNativeEventArgs>();
        public IntPtr Handle { get; set; }
        protected IntPtr HostWindowHandle { get; set; }
        public bool IsVip { get; private set; } = true;
        /// <summary>
        /// 获取webview大小
        /// </summary>
        
        bool is_transparent = false;
        /// <summary>
        /// webview是否透明
        /// </summary>
        public bool Transparent
        {
            get
            {
                return is_transparent;
            }
            set
            {
                is_transparent = value;
                MBVip.mbSetTransparent(Handle, value);
            }
        }
        public bool LoadUrlEndEnable { get; set; } = true;

        public Size Size => throw new NotImplementedException();

        public event EventHandler<TitleChangedEventArgs>? TitleChanged;
        public event EventHandler<LoadUrlBeginEventArgs>? LoadUrlBegin;
        public event EventHandler<DocumentReadyEventArgs>? DocumentReady;
        public event EventHandler<LoadUrlEndEventArgs>? LoadUrlEnd;
        public event EventHandler<PaintUpdatedEventArgs>? PaintUpdated;
        public event EventHandler<PaintBitUpdatedEventArgs>? PaintBitUpdated;
        public event EventHandler<JsCallNativeEventArgs>? JsCallNative;
        protected void OnTitleChanged(object sender,TitleChangedEventArgs args)
        {
            this.TitleChanged?.Invoke(sender, args);
        }
        protected void OnLoadUrlBegin(object sender, LoadUrlBeginEventArgs args)
        {
            this.LoadUrlBegin?.Invoke(sender, args);
        }
        protected void OnDocumentReady(object sender, DocumentReadyEventArgs args)
        {
            this.DocumentReady?.Invoke(sender, args);
        }
        protected void OnLoadUrlEnd(object sender, LoadUrlEndEventArgs args)
        {
            this.LoadUrlEnd?.Invoke(sender, args);
        }
        protected void OnPaintUpdated(object sender, PaintUpdatedEventArgs args)
        {
            this.PaintUpdated?.Invoke(sender, args);
        }
        protected void OnPaintBitUpdated(object sender, PaintBitUpdatedEventArgs args)
        {
            this.PaintBitUpdated?.Invoke(sender, args);
        }
        protected void OnJsCallNative(object sender, JsCallNativeEventArgs args)
        {
            this.JsCallNative?.Invoke(sender, args);
        }

        #region 私有方法
        MBVip.mbPaintUpdatedCallback? PaintUpdatedCallback;
        void MbOnPaintUpdated()
        {
            if (PaintUpdatedCallback == null)
            {
                PaintUpdatedCallback = (IntPtr webView, IntPtr param, IntPtr hdc, int x,int y, int width, int height) =>
                {
                    var arg = new PaintUpdatedEventArgs(this,hdc,x,y,width,height);
                    this.OnPaintUpdated(this, arg);
                };
            }
            MBVip.mbOnPaintUpdated(Handle, PaintUpdatedCallback, IntPtr.Zero);
        }
        object _lockMbOnPaintBitUpdated = new object();
        MBVip.mbPaintBitUpdatedCallback? PaintBitUpdatedCallback;
        void MbOnPaintBitUpdated()
        {
            if (PaintBitUpdatedCallback == null)
            {
                PaintBitUpdatedCallback = (IntPtr webView, IntPtr param, IntPtr buffer, ref MBVip.mbRect mbRect, int width, int height) =>
                {
                    if (this.PaintBitUpdated != null) {
                        unsafe {
                            lock (_lockMbOnPaintBitUpdated)
                            {
                                if (buffer == IntPtr.Zero)
                                    return;
                                var len = 4 * width * height;
                                var datas =new UnmanagedMemoryStream((byte*)buffer.ToPointer(),len);
                                var rect = new Rect();
                                rect.X = mbRect.x;
                                rect.Y = mbRect.y;
                                rect.Width = mbRect.w;
                                rect.Height = mbRect.h;
                                var args = new PaintBitUpdatedEventArgs(this, datas, rect, width, height);
                                this.OnPaintBitUpdated(this, args);
                            }
                        }
                    }
                };
            }
            MBVip.mbOnPaintBitUpdated(Handle, PaintBitUpdatedCallback, IntPtr.Zero);
        }
        MBVip.mbTitleChangedCallback? TitleChangedCallback;
        void MbOnTitleChanged()
        {
            if (TitleChangedCallback == null)
            {
                TitleChangedCallback = (WebView, param, title) =>
                {
                    var args = new TitleChangedEventArgs(title);
                    this.OnTitleChanged(this, args);
                };
            }
            MBVip.mbOnTitleChanged(this.Handle, TitleChangedCallback, IntPtr.Zero);
        }
        MBVip.mbLoadUrlBeginCallback? LoadUrlBeginCallback;
        void MbOnLoadUrlBegin()
        {
            if (LoadUrlBeginCallback == null)
            {
                LoadUrlBeginCallback = (webView, param, url, job) => {
                    if (this.LoadUrlBegin != null)
                    {
                        var args = new LoadUrlBeginEventArgs(this, url);
                        this.LoadUrlBegin(this, args);
                        if (args.DataStream!=null)
                        {
                            using var stream = args.DataStream;
                            var datas=new byte[stream.Length];
                            stream.Position = 0;
                            stream.Read(datas, 0, datas.Length);
                            var dataPtr = Marshal.AllocHGlobal((int)stream.Length);
                            Marshal.Copy(datas, 0, dataPtr, datas.Length);
                            MBVip.mbNetSetData(job, dataPtr, datas.Length);
                            Marshal.FreeHGlobal(dataPtr);
                            args.DataStream = null;
                            return true;
                        }
                        else
                        {
                            if (this.LoadUrlEndEnable)
                            {
                                MBVip.mbNetHookRequest(job);
                            }
                        }
                    }
                    return false;
                };
            }
            MBVip.mbOnLoadUrlBegin(Handle, LoadUrlBeginCallback, IntPtr.Zero);
        }
        MBVip.mbDocumentReadyCallback? DocumentReadyCallback;
        void MbOnDocumentReadyCall()
        {
            if (DocumentReadyCallback == null)
            {
                DocumentReadyCallback = (webview, param, frameId) =>
                {
                    this.OnDocumentReady(this, new DocumentReadyEventArgs(frameId));
                };
            }
            MBVip.mbOnDocumentReady(this.Handle,this.DocumentReadyCallback,IntPtr.Zero);
        }
        MBVip.mbLoadUrlEndCallback? LoadUrlEndCallback;
        void MbOnLoadUrlEnd()
        {
            if (LoadUrlEndCallback == null)
            {
                LoadUrlEndCallback = (webView, param, url, job, buf, len) =>
                {
                    var args = new LoadUrlEndEventArgs(this,url,buf,len);
                    this.OnLoadUrlEnd(this, args);
                    if (args.DataStream != null)
                    {
                        using var stream = args.DataStream;
                        var datas = new byte[stream.Length];
                        stream.Position = 0;
                        stream.Read(datas, 0, datas.Length);
                        var dataPtr = Marshal.AllocCoTaskMem(datas.Length);
                        Marshal.Copy(datas, 0, dataPtr, datas.Length);
                        MBVip.mbNetSetData(job, dataPtr, datas.Length);
                        Marshal.FreeCoTaskMem(dataPtr);
                        args.DataStream = null;
                    }
                };
            }
            MBVip.mbOnLoadUrlEnd(Handle, LoadUrlEndCallback, IntPtr.Zero);
        }
        MBVip.mbJsQueryCallback? JsQueryCallback;
        void MbJsQuery()
        {
            if (JsQueryCallback == null)
            {
                JsQueryCallback = (webView,  param,  es,  queryId,  messageId,  request) => {
                    JsCallNativeEventArgs args = new JsCallNativeEventArgs(this,queryId,messageId,request);
                    lock (js_call_native_event_arg_dic_lock)
                    {
                        js_call_native_event_arg_dic.Add(queryId, args);
                    }
                    this.OnJsCallNative(this,args);
                };
            }
            MBVip.mbOnJsQuery(this.Handle, JsQueryCallback, IntPtr.Zero);
        }
        #endregion
        /// <summary>
        /// 构造WebView
        /// </summary>
        public VipWebView(IntPtr parentHandle)
        {
            HostWindowHandle = parentHandle;
            if (parentHandle != IntPtr.Zero)
            {
                Win32Api.RECT rect;
                Win32Api.GetClientRect(parentHandle, out rect);
                var x = rect.left;
                var y = rect.top;
                var width = rect.right - rect.left;
                var height = rect.bottom - rect.top;
                Handle = MBVip.mbCreateWebWindow(MBVip.mbWindowType.MB_WINDOW_TYPE_CONTROL, parentHandle, x, y, width, height);
                MBVip.mbSetAutoDrawToHwnd(Handle, true);
                MBVip.mbSetResourceGc(Handle, 1);

            }
            else
            {
                Handle = MBVip.mbCreateWebView();
            }

            MbOnTitleChanged();
            MbOnPaintUpdated();
            MbOnPaintBitUpdated();
            MbOnLoadUrlBegin();
            MbOnLoadUrlEnd();
            MbJsQuery();
        }
        public void Resize(int width,int height)
        {
            MBVip.mbResize(Handle, width, height);
        }
        /// <summary>
        /// 加载URL
        /// </summary>
        /// <param name="url"></param>
        public void LoadUrl(string url)
        {
            MBVip.mbLoadURL(Handle, url.ToString());
        }
        /// <summary>
        /// 触发键盘按下消息
        /// </summary>
        /// <param name="virtualKeyCode">虚拟键值</param>
        /// <param name="flags"></param>
        /// <param name="systemKey">是否系统键</param>
        /// <returns></returns>
        public bool FireKeyDownEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBVip.mbFireKeyDownEvent(Handle, virtualKeyCode, flags, systemKey);
        }

        public bool FireKeyUpEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBVip.mbFireKeyUpEvent(Handle, virtualKeyCode, flags, systemKey);
        }

        public void SetFocus()
        {
            MBVip.mbSetFocus(Handle);
        }

        public void KillFocus()
        {
            MBVip.mbKillFocus(Handle);
        }

        public bool FireKeyPressEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBVip.mbFireKeyPressEvent(Handle, virtualKeyCode, flags, systemKey);
        }
        public bool FireWindowsMessage(uint message,uint wParam, uint lParam, out int result)
        {
            return MBVip.mbFireWindowsMessage(Handle, this.HostWindowHandle, message, wParam, lParam, out result);
        }

        public bool FireMouseWheelEvent(int x, int y, int delta, uint flags)
        {
            return MBVip.mbFireMouseWheelEvent(Handle, x, y, delta, flags);
        }

        public bool FireMouseEvent(uint message, int x, int y, uint flags)
        {
            return MBVip.mbFireMouseEvent(Handle, message, x, y, flags);
        }

        public CursorType GetCursorType()
        {
            return (CursorType)MBVip.mbGetCursorInfoType(Handle);
        }
        ManualResetEvent _mreRunjs = new ManualResetEvent(true);
        public Task<string?> RunJsAsync(string script)
        {
            _mreRunjs.WaitOne();
            _mreRunjs.Reset();
            TaskCompletionSource<string?> tcs = new TaskCompletionSource<string?>();
            var frame = MBVip.mbWebFrameGetMainFrame(this.Handle);
            MBVip.mbRunJs(this.Handle, frame, script, true, (view, p, es, jv) => {
                var jsType = MBVip.mbGetJsValueType(es, jv);
                if (jsType == MBVip.mbJsType.kMbJsTypeString)
                {
                    tcs.SetResult(Marshal.PtrToStringUTF8(MBVip.mbJsToString(es, jv)));
                    _mreRunjs.Set();
                }
                else
                {
                    tcs.SetResult(null);
                    _mreRunjs.Set();
                }
            }, IntPtr.Zero, IntPtr.Zero);
            return tcs.Task;
        }
        public string? RunJs(string script)
        {
            return this.RunJsAsync(script).GetAwaiter().GetResult();
        }
        public void Dispose()
        {            
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        bool is_disposed = false;
        protected void Dispose(bool disposing)
        {
            if (this.Handle == IntPtr.Zero)
                throw new Exception("资源释放异常WebView不能为0");
            if (!is_disposed)
            {
                if (disposing)
                {
                    //Clean Up managed resources  
                }
                MBVip.mbDestroyWebView(this.Handle);
            }
            is_disposed = true;
        }

        public void ResponseJsCallNative(long queryId, int messageId, string response)
        {
            JsCallNativeEventArgs args;
            lock (js_call_native_event_arg_dic_lock)
            {
                args = js_call_native_event_arg_dic[queryId];
                js_call_native_event_arg_dic.Remove(queryId);
            }
            MBVip.mbResponseQuery(args.WebView.Handle, queryId, messageId, response);
        }
        public void SetPosition(int x, int y)
        {
            MBVip.mbSetHandleOffset(this.Handle, x, y);
        }

        public void SetDebugConfig(string debuString, string param)
        {
            if (debuString == null || param == null)
                return;
            MBVip.mbSetDebugConfig(this.Handle, debuString, param);
        }

        ~VipWebView()
        {
            Dispose(false);
        }
    }
}
